package com.tianji.learning.utils;

import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;

/**
 * LearningRecordDelayTaskHandler类负责处理学习记录的延迟任务。
 * 该类使用DelayQueue来处理延迟任务，并将任务持久化到数据库。
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class LearningRecordDelayTaskHandler {

    private final StringRedisTemplate redisTemplate; // Redis模板，用于缓存数据
    private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>(); // 延迟队列，存放延迟任务
    private final static String RECORD_KEY_TEMPLATE = "learning:record:{}"; // Redis键模板
    private final LearningRecordMapper recordMapper; // 学习记录的Mapper接口
    private final ILearningLessonService lessonService; // 学习课程的服务接口
    private static volatile boolean begin = true; // 标识任务处理是否开始

    /**
     * Bean 创建：
     * Spring 容器扫描到 LearningRecordDelayTaskHandler 类，并创建其实例。
     * 执行依赖注入，将 StringRedisTemplate、LearningRecordMapper 和 ILearningLessonService 注入到该实例中。
     * 调用 @PostConstruct 标注的 init() 方法，启动异步任务处理延迟任务。
     * Bean 销毁：
     * 当 Spring 容器关闭时，或者该 Bean 被销毁时（例如在应用程序关闭时）。
     * 调用 @PreDestroy 标注的 destroy() 方法，停止延迟任务处理。
     */
    /**
     * 初始化方法，在Bean创建后调用，开启异步任务处理
     */
    @PostConstruct
    public void init(){
        CompletableFuture.runAsync(this::handleDelayTask); // 异步处理延迟任务
    }

    /**
     * 销毁方法，在Bean销毁前调用，停止任务处理
     */
    @PreDestroy
    public void destroy(){
        log.debug("关闭学习记录处理的延迟任务");
        begin = false; // 停止任务处理
    }

    /**
     * 处理延迟任务的方法
     */
    private void handleDelayTask(){
        while (begin){
            try {
                // 1.尝试获取任务
                DelayTask<RecordTaskData> task = queue.take();
                log.debug("获取到要处理的播放记录任务");
                RecordTaskData data = task.getData();
                // 2.读取Redis缓存
                LearningRecord record = readRecordCache(data.getLessonId(), data.getSectionId());
                if (record == null) {
                    continue;
                }
                // 3.比较数据
                if(!Objects.equals(data.getMoment(), record.getMoment())){
                    // 4.如果不一致，播放进度在变化，无需持久化
                    continue;
                }
                // 5.如果一致，证明用户离开了视频，需要持久化
                // 5.1.更新学习记录
                record.setFinished(null);
                recordMapper.updateById(record);
                // 5.2.更新课表
                LearningLesson lesson = new LearningLesson();
                lesson.setId(data.getLessonId());
                lesson.setLatestSectionId(data.getSectionId());
                lesson.setLatestLearnTime(LocalDateTime.now());
                lessonService.updateById(lesson);

                log.debug("准备持久化学习记录信息");
            } catch (Exception e) {
                log.error("处理播放记录任务发生异常", e);
            }
        }
    }

    /**
     * 添加学习记录任务到延迟队列
     *
     * @param record 学习记录对象
     */
    public void addLearningRecordTask(LearningRecord record){
        // 1.添加数据到Redis缓存
        writeRecordCache(record);
        // 2.提交延迟任务到延迟队列 DelayQueue
        queue.add(new DelayTask<>(new RecordTaskData(record), Duration.ofSeconds(20))); // 延迟20秒
    }

    /**
     * 写入学习记录到Redis缓存
     *
     * @param record 学习记录对象
     */
    public void writeRecordCache(LearningRecord record) {
        log.debug("更新学习记录的缓存数据");
        try {
            // 1.数据转换
            String json = JsonUtils.toJsonStr(new RecordCacheData(record));
            // 2.写入Redis
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId());
            redisTemplate.opsForHash().put(key, record.getSectionId().toString(), json);
            // 3.添加缓存过期时间
            redisTemplate.expire(key, Duration.ofMinutes(1)); // 缓存过期时间1分钟
        } catch (Exception e) {
            log.error("更新学习记录缓存异常", e);
        }
    }

    /**
     * 从Redis缓存中读取学习记录
     *
     * @param lessonId 课程ID
     * @param sectionId 小节ID
     * @return 学习记录对象
     */
    public LearningRecord readRecordCache(Long lessonId, Long sectionId){
        try {
            // 1.读取Redis数据
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
            Object cacheData = redisTemplate.opsForHash().get(key, sectionId.toString());
            if (cacheData == null) {
                return null;
            }
            // 2.数据检查和转换
            return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);
        } catch (Exception e) {
            log.error("缓存读取异常", e);
            return null;
        }
    }

    /**
     * 删除Redis缓存中的学习记录
     *
     * @param lessonId 课程ID
     * @param sectionId 小节ID
     */
    public void cleanRecordCache(Long lessonId, Long sectionId){
        // 删除数据
        String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
        redisTemplate.opsForHash().delete(key, sectionId.toString());
    }

    /**
     * RecordCacheData类用于表示缓存中的学习记录数据
     */
    @Data
    @NoArgsConstructor
    private static class RecordCacheData{
        private Long id; // 记录ID
        private Integer moment; // 播放进度
        private Boolean finished; // 是否完成

        public RecordCacheData(LearningRecord record) {
            this.id = record.getId();
            this.moment = record.getMoment();
            this.finished = record.getFinished();
        }
    }

    /**
     * RecordTaskData类用于表示延迟任务中的数据
     */
    @Data
    @NoArgsConstructor
    private static class RecordTaskData{
        private Long lessonId; // 课程ID
        private Long sectionId; // 小节ID
        private Integer moment; // 播放进度

        public RecordTaskData(LearningRecord record) {
            this.lessonId = record.getLessonId();
            this.sectionId = record.getSectionId();
            this.moment = record.getMoment();
        }
    }
}
